"""
Case Type   : 页面修复功能测试
Case Name   : 模拟页面损坏及修复测试（参数始终开启）
Create At   : 2025/09/16
Owner       : jiangyan
Description :
    1、开启页面修复参数
    2、创建astore表并插入数据
    3、获取表文件路径
    4、重启集群使参数生效
    5、在备机构造页面损坏
    6、检查页面损坏情况
    7、验证数据受损情况
    8、修改数据触发备机回放和修复
    9、检查表是否修复
Expect      :
    1、参数开启成功
    2、表创建和数据插入成功
    3、成功获取文件路径
    4、重启成功，参数生效
    5、页面损坏构造成功
    6、页面确认损坏
    7、数据访问出现错误
    8、数据修改成功触发回放和修复
    9、页面修复成功，数据可正常访问
History     :
"""

import unittest
import os
import time
from yat.test import macro
from yat.test import Node
from testcase.utils.Logger import Logger
from testcase.utils.CommonSH import CommonSH
from testcase.utils.Common import Common
from testcase.utils.Constant import Constant
from testcase.utils.ComThread import ComThread

logger = Logger()
COMMONSH = CommonSH("PrimaryDbUser")
STANDBY_SH = CommonSH("Standby1DbUser")
pri_node = Node("PrimaryDbUser")
standby_node = Node("Standby1DbUser")

class PageRepairTestCase(unittest.TestCase):
    def setUp(self):
        logger.info("==Opengauss_Function_Guc_Page_Repair_Case0003测试开始==")
        self.constant = Constant()
        # 检查数据库集群状态
        is_started = COMMONSH.get_db_cluster_status()
        self.assertTrue("Degraded" in is_started or "Normal" in is_started)

        # 步骤1：确保页面修复参数是打开的
        logger.info("步骤1：开启页面修复参数")
        result = COMMONSH.execute_gsguc("reload",
                                        self.constant.GSGUC_SUCCESS_MSG,
                                        "page_repair_function=on",
                                        node_name="all")
        self.assertTrue(result)
        
        # 重启集群使参数生效
        COMMONSH.restart_db_cluster()
        status = COMMONSH.get_db_cluster_status()
        self.assertTrue("Degraded" in status or "Normal" in status)
        
        # 验证参数已开启
        self.verify_parameter_value("on")

    def verify_parameter_value(self, expected_value):
        """验证参数值是否正确"""
        sql_cmd = "show page_repair_function;"
        result = COMMONSH.execut_db_sql(sql_cmd)
        logger.info(f"参数查询结果: {result}")

        # 统一转换为小写便于处理
        result_lower = result.lower()

        # 先尝试简单匹配常见格式
        if f"page_repair_function | {expected_value}" in result_lower:
            logger.info(f"参数值验证成功: {expected_value}")
            return True

        # 解析参数值的简化逻辑
        actual_value = self._parse_parameter_value(result)

        if actual_value is None:
            self.fail(f"无法解析参数值，查询结果: {result}")

        logger.info(f"解析到的参数值: {actual_value}, 期望值: {expected_value}")
        self.assertEqual(actual_value, expected_value, 
                       f"参数值应为{expected_value}，但实际为{actual_value}")
        return True

    def _parse_parameter_value(self, result):
        """解析参数值的辅助函数"""
        lines = result.strip().split('\n')

        for line in lines:
            line = line.strip().lower()
        
            # 跳过无关行
            if not line or '----' in line or '(1 row)' in line:
                continue
        
            # 直接提取参数值
            value = self._extract_value_from_line(line)
            if value:
                return value

        # 如果上面没找到，尝试全局查找
        return self._find_value_in_result(result.lower())

    def _extract_value_from_line(self, line):
        """从单行文本中提取参数值"""
        # 格式: page_repair_function | off
        if 'page_repair_function' in line and '|' in line:
            parts = line.split('|')
            if len(parts) > 1:
                return parts[1].strip()

        # 格式: page_repair_function off
        if 'page_repair_function' in line:
            parts = line.split()
            if len(parts) > 1:
                return parts[1].strip()

        # 格式: off (单独一行)
        if line in ['on', 'off']:
            return line

        return None

    def _find_value_in_result(self, result_lower):
        """在整个结果中查找参数值"""
        if ' off' in result_lower and ' on' not in result_lower.replace('page_repair_function', ''):
            return 'off'
        elif ' on' in result_lower and ' off' not in result_lower.replace('page_repair_function', ''):
            return 'on'
        return None

    def test_page_repair(self):
        # 步骤2：创建astore表
        logger.info("步骤2：创建astore表")
        sql_cmd = """
            drop table if exists test_table;
            create table test_table(a int, b char(50));
        """
        result = COMMONSH.execut_db_sql(sql_cmd)
        logger.info(result)
        self.assertIn(self.constant.CREATE_TABLE_SUCCESS, result)

        # 步骤3：向表中插入数据
        logger.info("步骤3：向表中插入数据")
        sql_cmd = """
            insert into test_table values (generate_series(1,250), 'aaa');
            insert into test_table values (generate_series(251,500), 'bbb');
        """
        result = COMMONSH.execut_db_sql(sql_cmd)
        logger.info(result)
        self.assertIn("INSERT", result)

        # 步骤4：检查表所在位置
        logger.info("步骤4：检查表文件路径")
        sql_cmd = "select pg_relation_filepath('test_table');"
        result = COMMONSH.execut_db_sql(sql_cmd)
        logger.info(result)
        self.assertIn("base/", result)
        file_path = result.strip().split('\n')[-2].strip()
        self.file_name = os.path.basename(file_path)
        self.dir_path = os.path.dirname(file_path)
        logger.info(f"表文件: {self.file_name}, 目录: {self.dir_path}")

        # 步骤5：在备机构造页面损坏
        logger.info("步骤5：在备机构造页面损坏")
        # 获取PGDATA路径
        pgdata_path = macro.DB_INSTANCE_PATH
        
        # 构造全零文件并损坏页面
        full_file_path = f"{pgdata_path}/{file_path}"
        damage_cmds = [
            f"cd {pgdata_path}/{self.dir_path}",
            f"cp {self.file_name} {self.file_name}.bak",  # 备份原文件
            "tr '\\000' '\\000' < /dev/zero | dd of=allzeros bs=1c count=8192",
            f"dd if=allzeros of=./{self.file_name} bs=8192 seek=0 count=1 conv=notrunc"
        ]
        
        for cmd in damage_cmds:
            result = standby_node.sh(cmd).result()
            logger.info(f"执行命令: {cmd}, 结果: {result}")

        # 重启集群使数据落盘
        COMMONSH.restart_db_cluster()
        status = COMMONSH.get_db_cluster_status()
        self.assertTrue("Degraded" in status or "Normal" in status)
        time.sleep(5)

        # 步骤6：修改表数据触发备机回放和修复
        logger.info("步骤6：修改数据触发修复")
        sql_cmd = "update test_table set b = 'ccc' where a = 50;"
        result = COMMONSH.execut_db_sql(sql_cmd)
        logger.info(result)
        self.assertIn("UPDATE", result)

         # 等待修复完成
        time.sleep(5)

         # 步骤7：检查表是否修复
        logger.info("步骤7：检查表是否修复")
        sql_cmd = "select count(*) from test_table;"
        result = COMMONSH.execut_db_sql(sql_cmd)
        logger.info(result)
         # 期望能够正常查询到500条记录，说明页面已修复
        self.assertIn("500", result)

         # 验证数据完整性
        logger.info("验证数据完整性")
        sql_cmd = "select distinct b from test_table order by b;"
        result = COMMONSH.execut_db_sql(sql_cmd)
        logger.info(result)
         # 应该包含原始数据和修改后的数据
        self.assertIn("aaa", result)
        self.assertIn("bbb", result)
        self.assertIn("ccc", result)

    def tearDown(self):
        logger.info("恢复环境")
        # 清理测试表
        sql_cmd = "drop table if exists test_table;"
        COMMONSH.execut_db_sql(sql_cmd)
        
        # 在备机恢复备份的文件（如果存在）
        try:
            pgdata_path = macro.DB_INSTANCE_PATH
            restore_cmd = f"cd {pgdata_path}/{self.dir_path}; [ -f {self.file_name}.bak ] && mv {self.file_name}.bak {self.file_name} || true"
            standby_node.sh(restore_cmd)
        except Exception as e:
            logger.info(f"恢复备份文件时出错: {e}")
        
        # 保持页面修复参数为开启状态（根据测试需求）
        # 如果需要恢复为默认值，取消下面的注释

        COMMONSH.execute_gsguc("reload",
                               self.constant.GSGUC_SUCCESS_MSG,
                               "page_repair_function=off",
                               node_name="all")
        COMMONSH.restart_db_cluster()
        
        # 检查集群状态
        is_started = COMMONSH.get_db_cluster_status()
        self.assertTrue("Degraded" in is_started or "Normal" in is_started)
        logger.info("==Opengauss_Function_Guc_Page_Repair_Case0003测试结束==")